// 
// Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;

using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Core;
using Galaxium.Protocol.Xmpp.Library.Messaging;

using Galaxium.Protocol;
using Galaxium.Client;
using Galaxium.Core;

using Anculus.Core;

namespace Galaxium.Protocol.Xmpp
{
	public class XmppConversation: AbstractConversation
	{
		public event EventHandler ResourceChanged;
		public event EventHandler StateChanged;
		public event EventHandler<MessageEventArgs> MessageSent;
		
		private Library.Messaging.Conversation _conversation;

		public XmppContact Contact { get; private set; }
		
		public ChatState State {
			get { return _conversation.ContactState; }
		}
		
		public string ActiveResource {
			get { return _conversation.CurrentResource; }
		}
		
		public override bool IsChannelConversation {
			get { return false; }
		}

		public override bool IsPrivateConversation {
			get { return true; }
		}

		public new XmppSession Session {
			get { return base.Session as XmppSession; }
		}
		
		public XmppConversation (XmppContact contact, XmppSession session)
			:base (contact, session)
		{
			Contact = contact;

			_conversation =	Session.Client.GetConversation (contact.InternalContact.Jid);
			
			_conversation.MessageAvailable +=
				(s, e) => ThreadUtility.Dispatch (DumpConversation);
			_conversation.ResourceChanged += 
				(s, e) => ThreadUtility.Dispatch (OnResourceChanged);
			_conversation.Closed +=
				(s, e) => ThreadUtility.Dispatch (Close);
			_conversation.StateChanged +=
				(s, e) => ThreadUtility.Dispatch (OnStateChanged);
			
			DumpConversation ();
		}
		
		#region Event emitors
		
		protected void OnResourceChanged ()
		{
			LogEvent ("Resource " + ActiveResource + " is now active.");
			
			if (ResourceChanged != null)
				ResourceChanged (this, EventArgs.Empty);
		}
		
		protected void OnMessageSent (Message message, XmppContact contact)
		{
			if (MessageSent != null)
				MessageSent (this, new MessageEventArgs (message, contact));
		}
		
		protected void OnReceivedMessageActivity (Message message)
		{
			var activity = new ReceivedMessageActivity (this, message);
			ActivityUtility.EmitActivity (this, activity);
		}
		
		protected void OnStateChanged ()
		{
			if (StateChanged != null)
				StateChanged (this, EventArgs.Empty);
		}
		
		#endregion
		
		private Message CreateMessage (XmlMessage msg)
		{
			MessageFlag flag;
			switch (msg.Type) {
			case MsgType.Chat:     flag = MessageFlag.Message; break;
			case MsgType.Headline: flag = MessageFlag.Event;   break;
			case MsgType.Error:    flag = MessageFlag.Error;   break;
			default:               throw new ArgumentException ();
			}
			
			IEntity source, destination;
			if (msg.From == null) {
				source = Session.Account;
				destination = Session.FindEntity (msg.To);
			}
			else {
				source = Session.FindEntity (msg.From);
				destination = Session.Account;
			}
			
			var text = (!msg.IsError) ? msg.Body : "Error: "
				+ msg.Error.GetHumanRepresentation () + "\n" + msg.Error.Description;
			
			DateTime stamp;
			JabberID src;
			string reason;
			if (!msg.GetDelay (out stamp, out src, out reason))
				stamp = msg.Received;
				
			var message = new Message (flag, source, destination, stamp);
			message.SetMarkup (text, null);
			return message;
		}

		private void DumpConversation ()
		{
			foreach (var msg in _conversation.GetQueue ()) {
				var message = CreateMessage (msg);
				LogMessage (message, Ready);
				if (msg.From == null) {
					OnMessageSent (message, Contact);
				} else {
					OnReceivedMessageActivity (message);
					OnMessageReceived (new MessageEventArgs (message, Contact));
				}
			}
		}

		public void ChangeState (ChatState state)
		{
			_conversation.ChangeState (state);
		}
		
		public override void InviteContact (IContact contact)
		{
			throw new NotImplementedException ();
		}

		public override void AddContact (IContact contact)
		{
			throw new InvalidOperationException ();
		}

		public override void RemoveContact (IContact contact)
		{
			throw new InvalidOperationException ();
		}
		
		public override void Close ()
		{
			OnClosed (new ConversationEventArgs (this));
		}

		public void SendMessage (string message)
		{
			Log.Debug ("Sending message.");
			_conversation.SendMessage (message);
			Log.Debug ("Message sent.");
		}
	}
}
